﻿using System;
using System.Configuration;
using System.IO;
using MLSharp.Classification;
using SVM;

namespace MLSharp.SupportVectorMachines
{
	/// <summary>
	/// A classifier factory that trains <see cref="LibSvmClassifier"/> instances.
	/// </summary>
	public class LibSvmClassifierFactory : IClassifierFactory
	{
		#region Logger

		/// <summary>
		/// The logger for the type.
		/// </summary>
		private static log4net.ILog mLogger =
			log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region Private Fields

		/// <summary>
		/// The directory to place temporary files in.
		/// </summary>
		private string mWorkingDirectory;

		#endregion

		#region Public Properties

		/// <summary>
		/// Gets the options used for training SVMs.
		/// </summary>
		public Parameter Options { get; private set; }

		#endregion

		#region Public Constructors

		/// <summary>
		/// Creates a classifier factory using path information from the 
		/// App.config file.
		/// </summary>
		public LibSvmClassifierFactory()
		{
			mWorkingDirectory = ConfigurationManager.AppSettings["LibSvmWorkingDirectory"] ?? @".\LibSvm";

			Initialize();
		}

		/// <summary>
		/// Creates a classifier factory that will use the specified path
		/// as it's temporary working directory.
		/// </summary>
		/// <param name="workingDirectory"></param>
		public LibSvmClassifierFactory(string workingDirectory)
		{
			mWorkingDirectory = workingDirectory;

			Initialize();
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Performs any necessary setup (for now, it just creates the working directory if it doesn't exist).
		/// </summary>
		private void Initialize()
		{
			Options = new Parameter();
			//This probably shouldn't even be exposed... if this is changed, nothing will work correctly.
			Options.Probability = true;

			mWorkingDirectory = Path.GetFullPath(mWorkingDirectory);

			if (!Directory.Exists(mWorkingDirectory))
			{
				Directory.CreateDirectory(mWorkingDirectory);
			}
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Gets a libsvm support vector machine trained from the specified data.
		/// </summary>
		/// <param name="trainingData">The training data to use.</param>
		/// <returns>The SVM.</returns>
		public LibSvmClassifier BuildSupportVectorMachine(IDataSet trainingData)
		{
			//Make sure the target attribute is binary.  For now, that's all this will work with...
			if (trainingData.Attributes[trainingData.TargetAttributeIndex].Type != AttributeType.Set
				|| trainingData.Attributes[trainingData.TargetAttributeIndex].PossibleValues.Length != 2)
			{
				throw new ArgumentException("Only binary target attributes are supported.");
			}

			//Load the training data
			Problem problem = new Problem(trainingData.Instances.Count, trainingData.GetClassLabels(), trainingData.GetInstancesAsNodes(), trainingData.Attributes.Count - 1);

			if (mLogger.IsDebugEnabled)
			{
				mLogger.Debug("Building support vector machine...");
			}

			//Build a model by training
			Model model = Training.Train(problem, Options);

			//Return the new classifier
			return new LibSvmClassifier(model);
		}

		#endregion

		#region Implementation of IClassifierFactory

		/// <summary>
		/// Builds a classifier to predict the specified attribute using the 
		/// specified training data.
		/// </summary>
		/// <param name="trainingData">The training data.</param>
		/// <returns>A classifier.</returns>
		IClassifier IClassifierFactory.BuildClassifier(IDataSet trainingData)
		{
			return BuildSupportVectorMachine(trainingData);
		}

		/// <summary>
		/// The options object for the factory.
		/// </summary>
		/// <remarks>
		/// Implementors should implement this property explicitly and expose 
		/// a strongly-typed options object instead.  
		/// </remarks>
		object IClassifierFactory.Options
		{
			get { return Options; }
		}

		#endregion
	}
}
